import { cloneDeep } from "lodash";
import { MapperFunction, SchemaResolverFunction } from "./types";

const schemaMap = {} as Record<string, any>;
const schemaResolverMap = {} as Record<string, SchemaResolverFunction>;

function getSchemaValueByDefault(defaultSchema: Record<string, any>): Record<string, any> {
    const { properties, title } = defaultSchema as Record<string, any>;
    const resolvedSchema = Object.keys(properties).reduce((propsObject: Record<string, any>, propKey: string) => {
        propsObject[propKey] = (properties[propKey].type === 'object' && !!properties[propKey].properties) ?
            getSchemaValueByDefault(properties[propKey]) : cloneDeep(properties[propKey].default);
        return propsObject;
    }, {});
    resolvedSchema.id = `${title}-${Date.now()}`;
    return resolvedSchema;
}

function getSchemaByType(componentType: string, resolveContext: Record<string, any> = {}): Record<string, any> | null {
    const defaulSchema = schemaMap[componentType];
    if (defaulSchema) {
        let componentSchema = getSchemaValueByDefault(defaulSchema);
        const schemaResolver = schemaResolverMap[componentType];
        componentSchema = schemaResolver ? schemaResolver({ getSchemaByType }, componentSchema, resolveContext) : componentSchema;
        return componentSchema;
    }
    return null;
}

function resolveSchema(schemaValue: Record<string, any>, defaultSchema: Record<string, any>): Record<string, any> {
    const { properties } = defaultSchema as Record<string, any>;

    const resolvedSchema = getSchemaValueByDefault(defaultSchema);

    Object.keys(schemaValue).reduce((resolvedSchema: Record<string, any>, propKey: string) => {
        resolvedSchema[propKey] = schemaValue[propKey];
        return resolvedSchema;
    }, resolvedSchema);

    return resolvedSchema;
};

function mappingSchemaToProps(resolvedSchema: Record<string, any>, schemaMapper: Map<string, string | MapperFunction>) {
    const props = Object.keys(resolvedSchema)
        .filter((propKey: string) => resolvedSchema[propKey] != null)
        .reduce((resolvedProps: Record<string, any>, propKey: string) => {
            if (schemaMapper.has(propKey)) {
                const mapper = schemaMapper.get(propKey) as string | MapperFunction;
                if (typeof mapper === 'string') {
                    resolvedProps[mapper] = resolvedSchema[propKey];
                } else {
                    const mapperResult = (mapper as MapperFunction)(propKey, resolvedSchema[propKey]);
                    Object.assign(resolvedProps, mapperResult);
                }
            } else {
                resolvedProps[propKey] = resolvedSchema[propKey];
            }
            return resolvedProps;
        }, {});
    return props;
}

function resolveSchemaToProps(
    schemaValue: Record<string, any>,
    defaultSchema: Record<string, any>,
    schemaMapper: Map<string, string | MapperFunction> = new Map()
): Record<string, any> {
    const resolvedSchema = resolveSchema(schemaValue, defaultSchema);
    const props = mappingSchemaToProps(resolvedSchema, schemaMapper);
    return props;
}

export { getSchemaByType, resolveSchemaToProps, schemaMap, schemaResolverMap };
